#include <fhe/core/FileSystem.h>
#include <fhe/core/Util.h>
#include <boost/filesystem.hpp>
#include <fstream>
#include <streambuf>

namespace fhe
{
  namespace core
  {
    FileSystem& FileSystem::instance()
    {
      static FileSystem fs;
      return fs;
    }
    
    FileSystem::FileSystem()
    {
      addPath( "." );
    }
    
    FileSystem::~FileSystem()
    {
    }
    
    void FileSystem::addPath( const std::string& path )
    {
      for ( boost::filesystem::directory_iterator i( path ); 
            i != boost::filesystem::directory_iterator(); ++i )
      {
        if ( boost::filesystem::is_directory( i->status() ) )
        {
          addPath( i->path().string() );
        }
        else
        {
          files_[ i->path().filename().string() ] = i->path().string();
        }
      }
    }
    
    bool FileSystem::hasFile( const std::string& filename ) const
    {
      return boost::filesystem::exists( boost::filesystem::path( filename ) ) || 
             files_.find( filename ) != files_.end();
    }
    
    std::string FileSystem::getFile( const std::string& filename ) const
    {
      if ( boost::filesystem::exists( boost::filesystem::path( filename ) ) ) 
      {
        return filename;
      }
      else
      {
        std::map< std::string, std::string >::const_iterator i = files_.find( filename );
        FHE_ASSERT_MSG( i != files_.end(), "unknown file %s", filename.c_str() );
        return i->second;
      }
    }
    
    std::string FileSystem::readFile( const std::string& filename ) const
    {
      std::ifstream fin( getFile( filename ).c_str() );
      std::string str;
      
      fin.seekg( 0, std::ios::end );
      str.reserve( fin.tellg() );
      fin.seekg( 0, std::ios::beg );
      
      str.assign( std::istreambuf_iterator<char>(fin), std::istreambuf_iterator<char>() );
      
      return str;
    }
    
    std::string FileSystem::getExtension( const std::string& filename ) const
    {
      return boost::filesystem::extension( filename );
    }
    
    bool FileSystem::hasEncoder( const std::string& extension ) const
    {
      for ( std::vector< Encoder::Ptr >::const_iterator dataStream = dataStreams_.begin();
            dataStream != dataStreams_.end(); ++dataStream )
      {
        if ( (*dataStream)->extension() == extension )
        {
          return true;
        }
      }
      return false;
    }
    
    Encoder::Ptr FileSystem::getEncoder( const std::string& extension ) const
    {
      for ( std::vector< Encoder::Ptr >::const_iterator dataStream = dataStreams_.begin();
            dataStream != dataStreams_.end(); ++dataStream )
      {
        if ( (*dataStream)->extension() == extension )
        {
          return *dataStream;
        }
      }
      FHE_ERROR( "unknown file extension %s", extension.c_str() );
    }
    
    void FileSystem::addEncoder( const Encoder::Ptr& dataStream )
    {
      FHE_ASSERT_MSG( !hasEncoder( dataStream->extension() ),
                      "duplicate file extension %s", dataStream->extension().c_str() );
      dataStreams_.push_back( dataStream );
    }
    
    Data::Ptr FileSystem::load( const std::string& filename ) const
    {
      return getEncoder( getExtension( filename ) )->load( filename );
    }
    
    void FileSystem::save( const Data::Ptr& data, const std::string& filename ) const
    {
      getEncoder( getExtension( filename ) )->save( data, filename );
    }

    bool FileSystem::hasSerializer( const std::string& name ) const
    {
      for ( std::vector< ISerializer::Ptr >::const_iterator serializer = serializers_.begin();
            serializer != serializers_.end(); ++serializer )
      {
        if ( (*serializer)->name() == name )
        {
          return true;
        }
      }
      return false;
    }

    bool FileSystem::hasSerializer( const std::type_info& type ) const
    {
      for ( std::vector< ISerializer::Ptr >::const_iterator serializer = serializers_.begin();
            serializer != serializers_.end(); ++serializer )
      {
        if ( (*serializer)->type() == type )
        {
          return true;
        }
      }
      return false;
    }
    
    ISerializer::Ptr FileSystem::getSerializer( const std::string& name ) const
    {
      for ( std::vector< ISerializer::Ptr >::const_iterator serializer = serializers_.begin();
            serializer != serializers_.end(); ++serializer )
      {
        if ( (*serializer)->name() == name )
        {
          return *serializer;
        }
      }
      FHE_ERROR( "unknown serializer name %s", name.c_str() );
    }
    
    ISerializer::Ptr FileSystem::getSerializer( const std::type_info& type ) const
    {
      for ( std::vector< ISerializer::Ptr >::const_iterator serializer = serializers_.begin();
            serializer != serializers_.end(); ++serializer )
      {
        if ( (*serializer)->type() == type )
        {
          return *serializer;
        }
      }
      FHE_ERROR( "unknown serializer type %s", type.name() );
    }

    void FileSystem::addSerializer( const ISerializer::Ptr& serializer )
    {
      FHE_ASSERT_MSG( !hasSerializer( serializer->name() ),
                      "duplicate serializer %s", serializer->name().c_str() );
      serializers_.push_back( serializer );
    }
  }
}
